package fons.navigator.views.piemap;


import java.awt.Color;
import java.awt.geom.Point2D;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;

import edu.umd.cs.piccolo.PNode;
import fons.model.ontology.Colors;
import fons.model.ontology.Dimension;
import fons.model.ontology.Filters;
import fons.model.ontology.Value;
import fons.model.ontology.ValueSpace;
import fons.navigator.views.ViewConfig;


public class PieChart implements Iterable<PieChart.PieSlice> {
    private static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat("#####.###");

	public static class PieSlice implements Comparable<PieSlice>, Anchorable {
    	private final Dimension part;
    	private final Dimension subPart;
    	private final String label;
    	
        private final double radius;
        private final double startAngle;
        private final double arcAngle;
        private final double value;
        private final double total;
    	private final Color color;
    	private final Color gradientColor;
    	private Point2D anchorPoint;
    	    	
        private PieSlice (String label, Dimension part, Dimension subPart, Color color, Color gradientColor,
        				 double startAngle, double value, double total, double radius) {
        	this.label = label;
            this.part = part;
            this.subPart = subPart;
            this.startAngle = startAngle;
            this.value = value;
            this.arcAngle = value * 360 / total;
            this.radius = radius;
            this.color = color;
            this.gradientColor = gradientColor;
            this.total = total;
        }
        
        public final long getPerMille () {
        	return Math.round((value/total)*1000); 
        }
        
        private final double getAnchorPointX () {
            return Math.sin(Math.toRadians(getMidAngle()+90))*radius/1.3;
        }
        
        private final double getAnchorPointY () {
            return Math.cos(Math.toRadians(getMidAngle()+90))*radius/1.3;
        }        

        final public double getStartAngle() {
            return startAngle;
        }

        final public double getArcAngle() {
            return arcAngle;
        }
        
        final public double getEndAngle() {
            return startAngle+arcAngle;
        }
        
        final public double getValue() {
            return value;
        }
        
        final public double getMidAngle() {
            return startAngle+arcAngle/2.0D;
        }
            
        @Override
        final public int compareTo(PieChart.PieSlice o) {
            return (int) (value - o.value);
        }
        
        @Override
        final public boolean equals (Object other) {
            if (!(other instanceof PieSlice)) return false;
            PieSlice o = (PieSlice) other;
            return this.part == o.part && this.subPart == o.subPart && this.label.equals(o.label);
        }
        
        @Override
        final public int hashCode () {
            return (int) (1000*value);
        }

        @Override
        public Point2D getAnchorOn(PNode node) {
            if (anchorPoint == null) {
                synchronized(this) {
                    if (anchorPoint == null) {
                        Point2D tmp = new Point2D.Double(getAnchorPointX()+node.getX(), getAnchorPointY()+node.getY());
                        this.anchorPoint = tmp;
                    }
                }
            }
            return anchorPoint;
        }

		public Color getColor() {
			return this.color;
		}
		
		public boolean hasGradient() {
			return this.gradientColor != null;
		}
		
		public Color getGradientColor() {
			return this.gradientColor;
		}

		public double getRadius() {
			return this.radius;
		}

		public String getLabel() {
			return this.label; 
		}
    }
    
    private final List<PieSlice> slices;
    private final double total;
    private final double radius;
    
    private PieChart (final List<PieSlice> slices, double total, double radius) {
        if (slices.size() == 0) {
        	total = 20;
        	radius = 20;
        	slices.add(new PieSlice(null, null, null, Color.GRAY, null, 0, 2, 2, 5));
        } 
        
        this.slices = slices;
        this.total = total;
        this.radius = radius;
    }
    
    @Override
    public boolean equals (Object other) {
        if (!(other instanceof PieChart)) {
            return false;
        }
        PieChart pc = (PieChart) other;
        
        return this.slices.equals(pc.slices);
    }
    
    @Override
    public int hashCode () {
        if (slices.size() == 0)
            return 17;
        else return
            slices.iterator().next().hashCode();
    }
    
    public final int size() {
        return slices.size();
    }
    public final double getRadius() {
    		return radius;
    }
    public final double getTotal () {
        return total;
    }
        
    public final Iterator<PieSlice> iterator () {
        return Collections.unmodifiableList(slices).iterator();
    }
    
    public static PieChart of (Target parent, final ValueSpace targetSpace,
    						   double radius, ViewConfig config) {
    	
    	List<Value> values = Lists.newArrayList();
    	values.addAll(targetSpace.filter(Filters.anyOf(parent.getPartDimensions())
    			.and(Filters.anyOf(parent.getSubParts()))));
    	Collections.sort(values, Ordering.from(Value.COMPARE_SAFELY).reverse());
       	    	
        final List<PieSlice> slices = new Vector<PieSlice>();
        
        double total = 0.0D;
        int count = 0;
                
        for (Value value: values) {        	
        	final double weight = value.getValue();
        	
        	if (!Double.isNaN(weight)) {
        		total += weight;
        		count++;
        	}
        }
        
        final int numSlices = parent.getPieMap().getLegendItemsCount();
        
        double firstValue = Double.NaN;
        double curValue = 0.0D;
        int counter = 0;
        
        for (final Value v: values) {
        	final Dimension part = v.hasAnyOf(parent.getPartDimensions());
        	//final Dimension filtered = v.hasAnyOf(parent.getFilterDimensions());       	
        	final Dimension subPart = v.hasAnyOf(parent.getSubParts());
        	double value = v.getValue();
            
            if (Double.isNaN(value))
                continue;
            if (firstValue == Double.NaN)
            	firstValue = value;
                       
            final double startAngle = curValue * 360 / total;
            
            if (counter > numSlices) {
            	slices.add(new PieSlice("Andere (insg. " + (count - counter) + " versch.): "
            			+ DEFAULT_DECIMAL_FORMAT.format(total-curValue) + " " 
						+ v.getMeasurement().getQualifiedName(),
						null, null,
            			Color.GRAY, null, startAngle, total-curValue, total, radius));
            	return new PieChart(slices, total, radius);
            } else {
            	if (counter == count-1)
            		value = total - curValue;
            		
            	curValue += value;
            	             	
            	slices.add(new PieSlice(
            				part.getName() +
            				(parent.getSubParts().size() > 1 ? (" - " + subPart.getName()) : "") +
            				": " + DEFAULT_DECIMAL_FORMAT.format(value)
            				+ " " + v.getMeasurement().getQualifiedName(),
            				part, subPart,
            				Colors.getColorFor(part, "node"),
            				((subPart != null && parent.getSubParts().size() > 1) 
            						? Colors.getColorFor(subPart, "node") : null),
            				startAngle, value, total, radius));
            	
            	Colors.setLabelFor("node", part, part.getName());
            	counter++;
            }
        }
        
        return new PieChart(slices, total, radius);
    }
}
